package com.segersten.kalaha

import java.util.logging.Logger;

import com.google.appengine.api.channel.ChannelServiceFactory
import com.google.appengine.api.channel.ChannelService
import com.google.appengine.repackaged.com.google.common.util.Base64
import com.google.appengine.repackaged.com.google.common.util.Base64DecoderException
import com.restfb.DefaultJsonMapper
import com.segersten.kalaha.util.authentication.SignedRequest
import com.restfb.FacebookClient
import com.restfb.DefaultFacebookClient
import com.restfb.types.User
import com.restfb.types.NamedFacebookType
import com.restfb.exception.FacebookOAuthException
import com.segersten.kalaha.Player
import com.segersten.kalaha.PlayerCache
import com.sun.org.apache.bcel.internal.generic.RETURN;

class StartController {

	def facebookKeys
	def bots
	private static Logger LOG = Logger.getLogger(getClass().getCanonicalName());
	
	def startResource = {
		if (params.clientSessionId) {
			LOG.info("LOGIN - params.clientSessionId=${params.clientSessionId}")
    		PlayerCache pc = new PlayerCache()
    		Player player = pc.getBySessionId(params.clientSessionId)
	    	if (null == player) {
	    		LOG.severe("Error:params.sessionId - Failed to get player")
				render(text:"Error")
				response.status = 500
	    		return
	    	} else {
				render(contentType:"text/json") {
					me = "http://${facebookKeys.callbackDomain}/players/${player.userId}"
					clientSessionId = player.sessionId
					day ="http://${facebookKeys.callbackDomain}/leaderboard/day"
					week ="http://${facebookKeys.callbackDomain}/leaderboard/week"
					total ="http://${facebookKeys.callbackDomain}/leaderboard/total"
				}
			}
    	} else if (params.botClientId) {
			if (bots.containsKey(params.botClientId)) {
				Player player = getBotPlayer(params.botClientId) 
				if (player!=null) {
					render(contentType:"text/json") {
						me = "http://${facebookKeys.callbackDomain}/players/${player.userId}"
						clientSessionId = player.sessionId
						day ="http://${facebookKeys.callbackDomain}/leaderboard/day"
						week ="http://${facebookKeys.callbackDomain}/leaderboard/week"
						total ="http://${facebookKeys.callbackDomain}/leaderboard/total"
					}
					return
				}
			}
			LOG.severe("Invalid bot client id " + params.botClientId)
			render(text:"Error")
			response.status = 500
			return
		} else {
			response.setHeader("Cache-Control","max-age=86400")
			render(contentType:"text/json") {
					day ="http://${facebookKeys.callbackDomain}/leaderboard/day"
					week ="http://${facebookKeys.callbackDomain}/leaderboard/week"
					total ="http://${facebookKeys.callbackDomain}/leaderboard/total"
				}
		}
	}
	
	def indexDebug2 = {
		def userId = params.userId ? params.userId : "DBG1234"+System.currentTimeMillis()
		def name = params.name ? params.name : "Kermit"+System.currentTimeMillis()
		Player player = new Player(sessionId:generateId(userId),
									accessToken:"DebugModeNoTokenAvailable",
									userId:userId,
									name:name,
									type:"DBG",
									created:new Date())
		def useChannel = params.noChannel==null
		storeInCache(player, useChannel)
		SessionTicket ticket = getTicket(player.userId) 
		int score = 0;
		PlayerScoreWeek ps = SessionTicket.find("SELECT FROM PlayerScoreWeek sc WHERE sc.userId = :id", [id:player.userId])
		if (ps != null) {
			score = ps.score
		}
		[ticket:ticket, player:player, score:score]
	}
	
	def indexDebug = {
		render(view:"index",model:indexDebug2(params))
	}
	
    def index = { 
    	Player player = null
    	SignedRequest sr = null
    	def data = null
    	
    	if (params.clientSessionId) {
			LOG.info("LOGIN - params.clientSessionId=${params.clientSessionId}")
    		// TODO change to HTTP error-code error handling
    		PlayerCache pc = new PlayerCache()
    		player = pc.getBySessionId(params.clientSessionId)
	    	if (!player) {
	    		render(text:"Error:params.sessionId - Failed to get player")
				response.status = 404
	    		return
	    	} 
    	} else if (params.signed_request) {
		LOG.info("LOGIN - params.signed_request")
    		def res = params.signed_request.tokenize("\\.")[1]
	    	data = base64decode(res)
	    	sr = getSignedRequest(data)
	    	
	    	if (!sr.oauthToken || !sr.userId) {
	    		render(text:"Error:signed_request<hr>${sr}<hr>${data}")
				response.status = 404
	    		return
	    	}
			LOG.info("LOGIN - sr.userId=${sr.userId}")
	    	
	    	// optimize fetching of player, look first in cache!!!!
	    	player = getFacebookUser(sr.userId, sr.oauthToken)
	    	storeInCache(player,true)
    	} else {
			LOG.info("LOGIN - accessToken=${params.code}")
			def redirectURI = "http://${facebookKeys.callbackDomain}/start/index"
			def rootUrl = "https://graph.facebook.com/oauth/access_token?client_id=${facebookKeys.clientCode}&client_secret=${facebookKeys.clientSecret}"
			String accessTokenURL = rootUrl + "&redirect_uri=${redirectURI}&code=${params.code.encodeAsURL()}"
			try {
				String result = new URL(accessTokenURL).getText()
				// Access token is first key=value pairs value.
				String accessToken = result.tokenize("&")[0].tokenize("=")[1]
				// Use a facebook client to request current logged in user friends.
				FacebookClient fb = new DefaultFacebookClient(accessToken)
			
				User me = fb.fetchObject("me", User.class)
				player = createPlayerObject(me)
				storeInCache(player,true)
			} catch(Exception e) {
				LOG.severe("Failed to authenticate " + e.getMessage());
    			render(text:"error")
				response.status = 500
				return
			}
    	}
    	
    	SessionTicket ticket = (player!=null) ? getTicket(player.userId) : null
		
		if (ticket!=null && ticket.sessionId!=null) {
			redirect(controller:'game', action:'game', params:[gameSessionId:ticket.sessionId, clientSessionId:player.sessionId])
			return
		}
		
		int score = 0;
		PlayerScoreWeek ps = PlayerScoreWeek.find("SELECT FROM PlayerScoreWeek sc WHERE sc.userId = :id", [id:player.userId])
		if (ps != null) {
			score = ps.score
		}
		
	    [ticket:ticket, player:player, sr:sr, data:data, score:score]
    }
    
    def base64decode = { String data ->
    	try {
    		new String(Base64.decode(data))
    	} catch(Base64DecoderException e) {
    		"error"
    	}
    }
    
    def storeInCache = { Player player, boolean useChannel -> 
		if (useChannel==true) {
			player.channelToken = Channel.createToken(player)
		}
		PlayerCache pc = new PlayerCache()
	    pc.put(player)
    }
    
    def getSignedRequest = { String json ->
    	DefaultJsonMapper mapper = new DefaultJsonMapper()
    	SignedRequest sr = mapper.toJavaObject(json, SignedRequest.class)
    	sr
    }
    
    def getTicket =  { String userId ->
    	SessionTicket.find("select from SessionTicket as s where s.ownerId = :p1", [p1:userId])
    }
    
    def generateId = { String userId ->
    	"${userId}${Math.abs(userId.hashCode())}"+System.currentTimeMillis()
    }
    
    def getFacebookUser = { String userId, String accessToken -> 
    		FacebookClient fb = new DefaultFacebookClient(accessToken)
    		try {
    			User user = fb.fetchObject("me", User.class)
    			return createPlayerObject(user)
    		} catch(FacebookOAuthException e) {
    			render("Error:failed to authenticate facebook user") 
    			return null
    		} catch(Exception e) {
    			render("Error:failed to get facebook user") 
    			return null
    		}
	}
	
	def createPlayerObject= { User user ->
		def fbuserId = "FB${user.id}"
		
		return new Player(sessionId:generateId(fbuserId),
					accessToken:null,
					 userId:fbuserId,
					name:user.name,
					type:"FB",
					created:new Date())
	}
	
	def getBotPlayer = { String clientId ->
		
		String botSettings = bots.get(clientId)
		if (botSettings==null) { return null }
		String []tokens = botSettings.split(",")
		String userId = "BOT"+tokens[0]
		String name = tokens[1]
		String callback = tokens[2]
		PlayerCache pc = new PlayerCache()
		Player player = pc.getByUserId(userId)
		if (player!=null) { return player }
		player = new Player(sessionId:generateId(userId),
			accessToken:callback,
			userId:userId,
			name:name,
			type:"BOT",
			created:new Date())
		storeInCache(player, false)
		return player
	}
    
}
